// ------------------------------------------------------------------------------------
// REVISION LOG ENTRY
// Revision By: (C) Copyright 2014 Hiroyuki Tominaga, All Rights Reserved.
//
// File name  : tkit-loader.js
// Revised on : 2014-07-01 04:12:29
// Summary    : Tkit primary script loader.
// CDN:
//    https://js-dragwindow.googlecode.com/svn/trunk/tkit-loader.js
//    *https://raw.githubusercontent.com/motrohi/js-projects/master/js-tkit/tkit-loader.js (ancillary
//
// Usage:
//    https://js-dragwindow.googlecode.com/svn/trunk/tkit-loader.js?depends=jquery,jsafx
//    https://js-dragwindow.googlecode.com/svn/trunk/tkit-loader.js?depends=1 (or true
//
//    https://js-dragwindow.googlecode.com/svn/trunk/tkit-loader.js?depends=1&devmode=1 (or true
//
//
// Required   : jsafx
/*

Specifications:

  1. location query parameter に defer=1 を渡すと, 主要な依存 script に defer attribute を付加します.
         -> tkit.html?defer=1

  2. tkit3.js#bindClasses では, まず class module が load されているかどうか検証し,
     not loaded の場合, load manager に module script の load request を出します.

     この時, bindClasses の parameter#3 に "tkit-loaded-class" のような string を渡した場合,
     window["tkitLoadedClass"] として instance が追加される object が bind されます.
     parameter#3 の文字列に "-" が混じっている場合, "-" は削除され直後の文字が大文字に変換された文字列になります.

     また, この bind は class の instance 構築, load request の process に入る前に行われるため,
     timer を使って進歩状況を観察することが可能です.

  3. すべての module が load されるといっせいに tkit class の instance 構築が始まります.

  4. すべて構築が完了後, bindClasses function に渡した callback function, および事前に
     load manager の "addLoadListener" で登録しておいた function すべてに instance が bind された plain object
     が渡されます.

     また, bindClasses function からも, instance がすべて bind された plain object が返却されます.

Specifications:

  1. If you pass a "defer=1" to location query parameter, you can add the defer attribute to the major dependent script.
        -> tkit.html?defer=1

  2. In tkit3.js # bindClasses, validates class module whether it is first load,
     If not loaded, I will issue a load request of module script to load manager.

  3. Instance construction of tkit class will begin simultaneously module of all is load.

  4. Construction is complete all, callback function that was passed to bindClasses function,
     and plain object to which the instance is to bind all function that has been registered with the "addLoadListener" of pre-load manager is passed.

     Also, from bindClasses function, plain object to which the instance is bind all is returned.

*/
// ------------------------------------------------------------------------------------

/**
 * class Tkit.*
 */
(function(name_space, loader_name)
{    /* Strict mode for this plugin */
    "use strict";

	// ----------------------------------- define closure section -----------------------------------
    var _DEBUG = false;
    var tkit; {
        var W = (window || self);
        /* */
        tkit = W[name_space];
        W.$ = W.$ || {};
        if (!tkit)
            tkit = W[name_space] = {};
        //
        if (!W.$.noop) {
            W.$.noop = function(){};
        }
    }

	var util = {
		// ---------------- constants.
	    CLASS_NAME: 0,
	    ELEMENT: 1,
	    CONFIG: 2,

	    PRIMARY: "tkit3",
		/* 内部 flag. */
	    PRIM_FLAG_JQ: 0x10,
	    PRIM_FLAG_JSAFX: 0x20,
	    PRIM_FLAG_TKIT: 0x30,

	    script_load: function () {
	        console.log("script_load, args=%o", arguments);
	        if (
	            //script && ( !script.readyState || /loaded|complete/.test(script.readyState) )
	            !this.readyState || /loaded|complete/.test(this.readyState)
	        ) {
	            // Handle memory leak in IE
	            this.onerror = this.onload = this.onreadystatechange = null;
	            closure_context_queue.length && loader.checkPending();
	            // Remove the script
	            if (!this.no_remove && this.parentNode) {
	                this.parentNode.removeChild(this);
	            }
	            //script = null;
	        }
	    },
	    // prototype にむやみに定義すると for(var i in object)
	    // で余計な値が列挙されるので, あまりしないほうが良い.
	    // hasOwnProperty(string) を使えば, 上記のようなことは回避できるが...
	    array_remove: function (any) {
	        for (var i = 0; i < this.length; i++) {
	            if (this[i] === any) {
	                this.splice(i, 1); break;
	            }
	        }
	    },
	    // from jsafx.parseCurrentQuery
		pcq: function () {
			var q = location.search;
			console.log(q);
			if (q) {
				var map = {}, pairs = q.substring(1).split("&");
				for (var i in pairs) {
					var pair = pairs[i].split("=");
					map[pair[0]] = pair[1];
				}
				return map;
			}
			return null;
		}
	};

	util.map = {
		jquery: util.PRIM_FLAG_JQ,
		jsafx: util.PRIM_FLAG_JSAFX
	};

    var loader;
	// "use strict" では暗黙的宣言は error となる.
    var
    closure_created_objects = {}, /* created instance place holder. */
    closure_context_queue = [],   /* pending class instance context queue(array */
    closure_load_listeners = [],  /* load event listener functions. */
    closure_pending_count = 0,    /* pending count */

    head = document.querySelectorAll('head')[0],
    // https://js-dragwindow.googlecode.com/svn/trunk/tkit-loader.js
    jquery_url = "https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min",
    jsafx_url = "https://js-extension-afx.googlecode.com/svn/trunk/jsafx",
    //var script_base: "https://js-dragwindow.googlecode.com/svn/trunk/",
    //var script_base: "https://raw.githubusercontent.com/motrohi/js-projects/master/js-tkit/",
    script_base = "https://js-dragwindow.googlecode.com/svn/trunk/";
//    script_base = "https://cdn.rawgit.com/motrohi/js-projects/master/js-tkit/";

	// ----------------------------------- define closure section -----------------------------------

    /**
     * get loader singleton instance.
     */
	tkit.getLoader = function() {
		return tkit[loader_name];
	};

	/** class module 定義時に利用.
	 * 自動的に constructor property が設定されるが, 記述がやや冗長になる.
	 */
	tkit.buildClass = function(className, ctor, proto) {
		var clazz = tkit[className] = ctor;
		clazz.qname = name_space + "." + className;
		clazz.prototype = proto;
		clazz.prototype.constructor = clazz;
	};
    /**
     * class Tkit.ScriptLoader (singleton
     */
    loader = tkit[loader_name] =
    {
    	query: util.pcq(),
    	// note: 2014-07-01 07:31:16 この function は export すべきではないかもしれない.
        init: function() {
			var scriptQuery = '';
			for (var scripts = document.scripts, i = scripts.length; --i >= 0;) {
				var script = scripts[i];
				var match = script.src.match(/^[^?#]*\/tkit-loader\.js(\?[^#]*)?(?:#.*)?$/);
				if (match) {
					scriptQuery = match[1] || '';
					// Remove the script from the DOM so that multiple runs at least run
					// multiple times even if parameter sets are interpreted in reverse
					// order.
					//script.parentNode.removeChild(script);
					break;
				}
			}
			var in_develop = false, depends_all = false;
			// https://js-dragwindow.googlecode.com/svn/trunk/tkit-loader.js?depends=jquery,jsafx ^[^?#]*\/tkit-loader\.js(\?[^#]*)?(?:#.*)?$
			if (scriptQuery.length) {
				scriptQuery.replace(/[?&]([^&=]+)=([^&]+)/g, function ($0, name, value) {
					value = value.split(",");
					if (value.length) {
						var sval = value[0].toLowerCase();
						var boolval = (sval === "1" || sval === "true");
						if (name === 'depends') {
							if (boolval) {
								depends_all = true;
							} else {
								value.forEach(function(e, i, arry) {
				            		loader.schedule(null, in_develop, util.map[e.toLowerCase()]);
								});
							}
						} else if (name === 'devmode' && boolval) {
							in_develop = true;
						}
					}
				});
			}
			// example.html?depends=1&devmode=1
			var q = this.query;
			if (q) {
				if (q.depends === "1" || q.depends === "true")
					depends_all = true;
				if (q.devmode === "1" || q.devmode === "true")
					in_develop = true;
			}

			if (depends_all) {
				this.schedule(null, in_develop, util.PRIM_FLAG_JQ);
				this.schedule(null, in_develop, util.PRIM_FLAG_JSAFX);
			}
            this.schedule(null, in_develop, util.PRIM_FLAG_TKIT);
        },

	    /**
	     * change load script base url.
	     */
		setLoaderBase: function(url) {
			if (!/\/$/.test(url))
				url += "/";
			script_base = url;
		},
        addLoadListener: function (l) {
            if (typeof l === "function") closure_load_listeners.push(l);
        },
        removeLoadListener: function (l) {
            if (typeof l === "function") {
                util.array_remove.call(closure_load_listeners, l);
            }
        },

		toScriptURL: function (className, in_develop, prim_flag) {
	        var postfix = in_develop? ".js?id=" + new Date().getTime(): ".js";
	        if (prim_flag === util.PRIM_FLAG_JQ)
	        	return jquery_url + postfix;
	        if (prim_flag === util.PRIM_FLAG_JSAFX)
	        	return jsafx_url + postfix;
	        var file = prim_flag === util.PRIM_FLAG_TKIT? util.PRIMARY: "tkit-" + className.toLowerCase();
	       	return script_base + file + postfix;
	    },

        exports: function(name_space) {
        	if (typeof name_space === "string")
        		window[name_space] = closure_created_objects;
        	//else
        	return closure_created_objects;
        },
        addInstance: function(instance) {
        	closure_created_objects[instance.id()] = instance;
        },
        // for debug.
        getQueue: function() {
        	return closure_context_queue;
        },

        /**
         * @param array      pass initialization context of class as an array.
         *                   [<className>, <html element>, <config id>]
         * @param in_develop usage for in development mode.
         * @param prim_flag    normally, this parameter is unnecessary. (internal use
         */
        schedule: function(array, in_develop, prim_flag)
        {
            if (array !== null) {
                closure_pending_count++;
                closure_context_queue.push(array);
            }

            var script = document.createElement('script');
            script.onload = script.onerror = script.onreadystatechange = function() {
            	if (prim_flag !== undefined)
            		this.no_remove = true;
            	util.script_load.apply(this, arguments);
            };
            script.type = 'text/javascript';
            if (this.query.defer/*prim_flag !== undefined*/)
            	script.defer = true;

            head.insertBefore(script, head.lastChild);
            // tkit-dev.js から head tag に埋め込むと, $.genarateId(5) が $(=jQuery が認識されない...
            script.src = this.toScriptURL(
                !prim_flag? array[util.CLASS_NAME]: null,
                in_develop, prim_flag
            );
            // note: 2014-06-08 16:21:34
            //    IE10 では parentNode, nextSilbling を使うと失敗する.
            //s.parentNode.insertBefore(script, s.nextSilbling);
        },
        checkPending: function checkPending() {
            if ( --closure_pending_count <= 0 && closure_context_queue.length > 0 ) {
                tkit.DEBUG && console.log("load done, closure_pending_count=%d", closure_pending_count);
                // [fqdn, element, config]
                closure_context_queue.forEach(function(e, i, arry) {
                    var tkit_class = new tkit[ e[util.CLASS_NAME] ]( e[util.ELEMENT] );
                    closure_created_objects[tkit_class.id()] = tkit_class;
                    tkit.DEBUG && console.log("_load_helper, class=%o", tkit_class);
                    tkit_class.setOptions(e[util.CONFIG]);
                });
                // Class load of all completed.
                callSequentially(closure_load_listeners, closure_created_objects); /* from jsafx.js */
                //this.release();
                closure_context_queue = [];   // initialize queue.
                closure_pending_count = 0;
            }
        },
		/**
		 * loader をもう利用しない場合 instance を cleanup 出来ます.
		 */
        release: function () {
        	if (closure_pending_count === 0) {
	            var flags = 0;
	            for (var i in this) {
	                if (this.hasOwnProperty(i)) // delete this[i] << 0; JSHint says "variables should not be deleted".
	                    flags = flags << 1 | (delete this[i]) << 0;
	            }
	            flags = flags << 1 | (delete tkit[loader_name]) << 0;
	            console.log("delete own=%s", flags.toString(2));
	            flags = 0;
	            for (var i in util) {
	                if (util.hasOwnProperty(i)) // delete this[i] << 0; JSHint says "variables should not be deleted".
	                    flags = flags << 1 | (delete util[i]) << 0;
	            }
	            console.log("delete util=%s", flags.toString(2));
			} else {
				console.log("load is not completed, cannot release");
			}
			/* NG in "use strict";
			delete closure_created_objects;
			delete closure_context_queue;
			delete closure_load_listeners;
			delete closure_pending_count;

			delete head;
			delete jquery_url;
			delete script_base;*/
        }
    };

    loader.qname = name_space + "." + loader_name;
    loader.init();
    //loader.schedule(null, true, true);

})("Tkit", "ScriptLoader");    // (function()

